/*	$Id: uri.c 1056 2010-04-21 03:51:21Z phrakt $	*/
/*
 * Copyright (c) 2003 Jean-Francois Brousseau <phrakt@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
/*
 * HTTP URI
 * ========
 *
 *
 * Sample URIs that should be parsable:
 *
 *	http://www.example.com/
 *	http://index.html
 *	http://user@www.example.com/
 *	http://user:pwd@www.example.com/
 *	http://www.example.com/somepath/index.html
 *	http://www.example.com:8080/somepath/index.html
 *	http://www.example.com/index.pl?queryarg1=foo&queryarg2=bar
 *	http://index.html#myfragment
 *	/index.html
 *	/my/path/to/script?qarg=foo&oarg=bar
 */

#include <sys/types.h>
#include <sys/socket.h>

#include <netinet/in.h>
#include <arpa/inet.h>

#include <errno.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <canopy/log.h>
#include <canopy/mem.h>
#include <canopy/string.h>

#include "uri.h"
#include "http.h"
#include "private.h"



/*
 * http_uri_alloc()
 *
 * Allocate a new empty URI structure and return it.  The URI's scheme is set
 * to `http' by default, and the port is set to the default HTTP port.
 * Returns a pointer to the HTTP URI structure on success, or NULL on failure.
 */
http_uri_t*
http_uri_alloc(void)
{
	http_uri_t *uri;

	if ((uri = cnp_malloc(sizeof(*uri))) == NULL) {
		http_log_errno("failed to allocate HTTP URI");
		return (NULL);
	}
	memset(uri, 0, sizeof(*uri));

	uri->uri_port = HTTP_PORT;
	if (http_uri_setscheme(uri, "http") == -1) {
		cnp_free(uri);
		return (NULL);
	}

	return (uri);
}

/*
 * http_uri_parse()
 *
 * Parse a Uniform Resource Identifier (URI) string <uri_str> and fill an
 * HTTP URI structure with all the fields on the original string.
 * Returns a pointer to the HTTP URI structure on success, or NULL on failure.
 */
http_uri_t*
http_uri_parse(const char *uri_str)
{
	char *copy, *sp, *ep;
	http_uri_t *uri;

	if ((uri = http_uri_alloc()) == NULL) {
		http_log_errno("failed to allocate HTTP URI");
		return (NULL);
	}

	if ((copy = cnp_strdup(uri_str)) == NULL) {
		http_uri_free(uri);
		return (NULL);
	}

	sp = copy;

	/* first get rid of fragment and query */
	ep = strchr(copy, HTTP_URI_FRAGDELIM);
	if (ep != NULL) {
		*(ep++) = '\0';

		if (http_uri_setfragment(uri, ep) == -1) {
			http_uri_free(uri);
			cnp_free(copy);
			return (NULL);
		}
	}

	if ((ep = strchr(copy, HTTP_URI_QUERYDELIM)) != NULL) {
		/* query is everything after the delimiter */
		*(ep++) = '\0';

		if (http_uri_setquery(uri, ep) == -1) {
			http_uri_free(uri);
			cnp_free(copy);
			return (NULL);
		}
	}

	/* look for a possible scheme */
	if (copy[0] != '/') {
		ep = strchr(copy, HTTP_URI_SCHEMEDELIM);
		if (ep == NULL) {
			http_log_err(
			    "missing scheme delimiter in URL `%s'",
			    uri_str);
			http_uri_free(uri);
			cnp_free(copy);
			return (NULL);
		}
		*(ep++) = '\0';

		if ((*ep != '/') || (*(ep + 1) != '/')) {
			http_log_err("malformed scheme delimiter");
			http_uri_free(uri);
			cnp_free(copy);
			return (NULL);
		}
		ep += 2;

		strlcpy(uri->uri_scheme, copy, sizeof(uri->uri_scheme));
		sp = ep;
	}

	/*
	 * At this point, we have no more scheme, and no trailing
	 * fragment or query parts left.
	 */
	ep = strchr(sp, HTTP_URI_PATHDELIM);
	if (ep != NULL) {
		http_uri_setpath(uri, ep);

		/* assume the rest is the host */
		if ((ep > sp) &&
		    (uri->uri_host = cnp_strndup(sp, ep - sp)) == NULL) {
			http_uri_free(uri);
			cnp_free(copy);
			return (NULL);
		}
	}
	else {
		if (http_uri_sethost(uri, sp) == -1) {
			http_uri_free(uri);
			cnp_free(copy);
			return (NULL);
		}

		http_uri_setpath(uri, "/");
	}

#if 0
	http_log_debug("\nParsed URI:\n===========\n"
	    "URI: %s\nScheme: %s\nHost:%s\nPort: %d\nPath: %s\nQuery: %s\n",
	    uri_str, uri->uri_scheme, uri->uri_host, uri->uri_port, uri->uri_path,
	    uri->uri_query);
#endif

	cnp_free(copy);

	return (uri);
}

/*
 * http_uri_format()
 *
 * Format the contents of HTTP URI structure <uri> into its string
 * representation and store the result in <dst>, which is of size <dlen>.
 * The resulting string is guaranteed to be NUL-terminated.
 *
 * Returns the number of bytes used, or -1 if an error occured.
 */
ssize_t
http_uri_format(http_uri_t *uri, char *dst, size_t dlen, int flags)
{
	size_t len = 0;
	char tmp[32];

	dst[0] = '\0';

	if (uri->uri_scheme[0] != '\0')
		snprintf(dst, dlen, "%s://", uri->uri_scheme);

	if (uri->uri_host != NULL) {
		strlcat(dst, uri->uri_host, dlen);

		if (uri->uri_port != 0) {
			snprintf(tmp, sizeof(tmp), ":%u", uri->uri_port);
			if (strlcat(dst, tmp, dlen) >= dlen) {
				return (-1);
			}
		}
	}

	if (strlcat(dst, uri->uri_path, dlen) >= dlen) {
		return (-1);
	}

	if (uri->uri_query != NULL) {
		strlcat(dst, "?", dlen);
		if (strlcat(dst, uri->uri_query, dlen) >= dlen)
			return (-1);
	}

	if (uri->uri_fragment != NULL) {
		strlcat(dst, "#", dlen);
		if (strlcat(dst, uri->uri_fragment, dlen) >= dlen)
			return (-1);
	}

	return (len);
}

/*
 * http_uri_getfragment()
 *
 * Returns the fragment component associated with the URI <uri>.  A retur
 * value equal to NULL indicates that no host was set for this URI.
 */
const char*
http_uri_getfragment(const http_uri_t *uri)
{
	return (uri->uri_fragment);
}

/*
 * http_uri_gethost()
 *
 * Returns the host component associated with the URI <uri>.  A return value
 * equal to NULL indicates that no host was set for this URI.
 */
const char*
http_uri_gethost(const http_uri_t *uri)
{
	return (uri->uri_host);
}

/*
 * http_uri_getpath()
 *
 * Returns the path component associated with the URI <uri>.  A return value
 * equal to NULL indicates that no path was set for this URI.
 */
const char*
http_uri_getpath(const http_uri_t *uri)
{
	return (uri->uri_path);
}

/*
 * http_uri_getport()
 *
 * Returns the port associated with the URI <uri>.
 */
int
http_uri_getport(const http_uri_t *uri)
{
	return (uri->uri_port);
}

/*
 * http_uri_getquery()
 *
 * Returns the query string associated with the URI <uri>.  A return value
 * equal to NULL indicates that no query was set for this URI.
 */
const char*
http_uri_getquery(const http_uri_t *uri)
{
	return (uri->uri_query);
}

/*
 * http_uri_getscheme()
 *
 * Returns the scheme associated with the URI <uri>.
 */
const char*
http_uri_getscheme(const http_uri_t *uri)
{
	return (uri->uri_scheme);
}

/*
 * http_uri_setfragment()
 *
 * Returns 0 on success, or -1 on failure.
 */
int
http_uri_setfragment(http_uri_t *uri, const char *fragment)
{
	char *tmp = NULL;

	if ((fragment != NULL) && ((tmp = cnp_strdup(fragment)) == NULL)) {
		http_log_err("failed to copy URI fragment");
		return (-1);
	}

	if (uri->uri_fragment != NULL)
		cnp_free(uri->uri_fragment);

	uri->uri_fragment = tmp;
	return (0);
}

/*
 * http_uri_sethost()
 *
 * Returns 0 on success, or -1 on failure.
 */
int
http_uri_sethost(http_uri_t *uri, const char *host)
{
	char *tmp = NULL;

	if ((host != NULL) && ((tmp = cnp_strdup(host)) == NULL)) {
		http_log_err("failed to copy URI host");
		return (-1);
	}

	if (uri->uri_host != NULL)
		cnp_free(uri->uri_host);

	uri->uri_host = tmp;
	return (0);
}

/*
 * http_uri_setpath()
 *
 *
 * Returns 0 on success, or -1 on failure.
 */
int
http_uri_setpath(http_uri_t *uri, const char *path)
{
	char *tmp = NULL;

	if (path != NULL) {
		if ((tmp = cnp_strdup(path)) == NULL) {
			http_log_err("failed to copy URI path");
			return (-1);
		}
	}

	if (uri->uri_path != NULL)
		cnp_free(uri->uri_path);

	uri->uri_path = tmp;
	return (0);
}

/*
 * http_uri_setport()
 *
 * Returns 0 on success, or -1 on failure.
 */
int
http_uri_setport(http_uri_t *uri, int port)
{
	if ((port < 0) || (port > 65535)) {
		return (-1);
	}

	uri->uri_port = port;
	return (0);
}

/*
 * http_uri_setquery()
 *
 * Set the query portion of the URI <uri> to the value specified in <query>.
 *
 * Returns 0 on success, or -1 on failure.
 */
int
http_uri_setquery(http_uri_t *uri, const char *query)
{
	char *tmp = NULL;

	if (query != NULL) {
		if ((tmp = cnp_strdup(query)) == NULL) {
			http_log_err("failed to copy URI query");
			return (-1);
		}
	}

	if (uri->uri_query != NULL)
		cnp_free(uri->uri_query);

	uri->uri_query = tmp;
	return (0);
}

/*
 * http_uri_setscheme()
 *
 * Set the scheme associated with the URI <uri> to the value specified
 * in <scheme>.
 *
 * Returns 0 on success, or -1 on failure.
 */
int
http_uri_setscheme(http_uri_t *uri, const char *scheme)
{
	size_t ret;

	ret = strlcpy(uri->uri_scheme, scheme, sizeof(uri->uri_scheme));
	if (ret >= sizeof(uri->uri_scheme)) {
		http_log_err("URI scheme `%s' is too long", scheme);
		return (-1);
	}

	return (0);
}

/*
 * http_uri_free()
 *
 * Free the URI structure pointed to by <vp> and all internal data.
 */
void
http_uri_free(http_uri_t *uri)
{
	if (uri->uri_user != NULL)
		cnp_free(uri->uri_user);

	if (uri->uri_host != NULL)
		cnp_free(uri->uri_host);

	if (uri->uri_path != NULL)
		cnp_free(uri->uri_path);

	if (uri->uri_fragment != NULL)
		cnp_free(uri->uri_fragment);

	if (uri->uri_query != NULL)
		cnp_free(uri->uri_query);

	cnp_free(uri);
}
